﻿#region Regions

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;

using FMWare.Core.Translations;

using Xceed.Compression;
using Xceed.Compression.Formats;

using BinaryReader=FMWare.Core.Base.BinaryReader;
using BinaryWriter=FMWare.Core.Base.BinaryWriter;

#endregion

namespace FMWare.Core.Databases
{
	/// <summary>
	/// Wrapper around the lang_db.dat file
	/// </summary>
	public static class LanguageDatabase
	{
		#region Declarations

		private const string FILE_NAME = "lang_db.dat";
		private static int _languagesProcessed;

		#endregion

		#region Constructors

		static LanguageDatabase()
		{
			// Initialize the XCeed library
			Global.SetXCeedLicense();

			// Init Data
			Data = new Dictionary<SupportedLanguages, LanguageData>();
		}

		#endregion

		#region Public Events

		public static event EventHandler<EventArgs> LanguageDataReadCompleted;

		#endregion

		#region Public Properties

		/// <summary>
		/// Gets a reference to all the translations.
		/// </summary>
		/// <value>The data.</value>
		public static Dictionary<SupportedLanguages, LanguageData> Data { get; private set; }

		/// <summary>
		/// Sets the debug binary dump path.
		/// </summary>
		/// <value>The debug binary dump path.</value>
		public static string DebugBinaryDumpPath { private get; set; }

		/// <summary>
		/// Sets the debug data dump path.
		/// </summary>
		/// <value>The debug data dump path.</value>
		public static string DebugDataDumpPath { private get; set; }

		#endregion

		#region Private Properties

		private static byte[] Header1 { get; set; }
		private static byte[] Header2 { get; set; }
		private static List<SupportedLanguages> LoadedLanguages { get; set; }
		private static byte NumberOfLanguagesInDatabase { get; set; }
		private static List<OffsetInfoData> OffsetInfo { get; set; }
		private static long StartOffset { get; set; }
		private static short Version { get; set; }

		#endregion

		#region Event Handlers

		private static void BackgroundWorkerForReading(object sender, DoWorkEventArgs e)
		{
			ReaderBackgroundWorker readerBackgroundWorker = (ReaderBackgroundWorker) sender;
			ReadLanguage(readerBackgroundWorker.FileName, readerBackgroundWorker.LanguageIndex, readerBackgroundWorker.Data);
		}

		private static void BackgroundWorkerForReadingEnded(object sender, RunWorkerCompletedEventArgs e)
		{
			_languagesProcessed++;
			ReaderBackgroundWorker readerBackgroundWorker = (ReaderBackgroundWorker) sender;
			if (readerBackgroundWorker.Languages[0] == SupportedLanguages.All && _languagesProcessed == 20)
			{
				if (LanguageDataReadCompleted != null)
					LanguageDataReadCompleted(null, EventArgs.Empty);
			}
			else if (readerBackgroundWorker.Languages[0] != SupportedLanguages.All && _languagesProcessed == readerBackgroundWorker.Languages.Count)
			{
				if (LanguageDataReadCompleted != null)
					LanguageDataReadCompleted(null, EventArgs.Empty);
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Loads the language database at the specified path.
		/// </summary>
		/// <param name="path">The path.</param>
		/// <param name="languages">The languages to include.</param>
		public static void Load(string path, List<SupportedLanguages> languages)
		{
			BinaryReader reader = new BinaryReader(File.OpenRead(path + FILE_NAME), Encoding.Unicode);
			LoadedLanguages = languages;

			// Read out header information
			Header1 = reader.ReadBytes(6);
			Version = reader.ReadInt16(); // 2 Bytes
			Header2 = reader.ReadBytes(1);

			// Check if this is a valid supported version
			Global.CheckSupportedVersion(Version);

			// Read number of languages found
			NumberOfLanguagesInDatabase = reader.ReadByte();
			Data = new Dictionary<SupportedLanguages, LanguageData>(NumberOfLanguagesInDatabase);

			// Keep reference to starting position
			StartOffset = reader.BaseStream.Position - 1;

			// First build list with offset entries
			OffsetInfo = new List<OffsetInfoData>();
			for (int i = 0; i < NumberOfLanguagesInDatabase; i++)
			{
				int languageId = reader.ReadInt32();
				int offset = reader.ReadInt32();
				OffsetInfo.Add(new OffsetInfoData {LanguageId = languageId, Offset = offset});
			}

			_languagesProcessed = 0;

			// Start reading
			for (int i = 0; i < NumberOfLanguagesInDatabase; i++)
			{
				// Create Language
				LanguageData languageData = new LanguageData();
				Data.Add((SupportedLanguages) OffsetInfo[i].LanguageId, languageData);

				if (LoadedLanguages.Contains(SupportedLanguages.All) || LoadedLanguages.Contains((SupportedLanguages) OffsetInfo[i].LanguageId))
				{
					ReaderBackgroundWorker worker = new ReaderBackgroundWorker();
					worker.LanguageIndex = i;
					worker.Data = languageData;
					worker.FileName = path + FILE_NAME;
					worker.Languages = languages;
					worker.DoWork += BackgroundWorkerForReading;
					worker.RunWorkerCompleted += BackgroundWorkerForReadingEnded;
					worker.RunWorkerAsync();
				}
			}

			reader.Close();
		}

		/// <summary>
		/// Saves the language database at the specified path.
		/// This method can only be called if ALL languages are loaded.
		/// </summary>
		/// <param name="path">The path.</param>
		public static void Save(string path)
		{
			if (!LoadedLanguages.Contains(SupportedLanguages.All))
				throw new ApplicationException("Cannot save " + FILE_NAME + " if not all languages are loaded.");

			BinaryWriter writer = new BinaryWriter(File.Create(path + FILE_NAME), Encoding.Unicode);
			writer.Write(Header1);
			writer.Write(Version);
			writer.Write(Header2);
			writer.Write(NumberOfLanguagesInDatabase);

			long offsetPosition = writer.BaseStream.Position;

			// first run, write default offsets got from reading, later overwrite
			for (int i = 0; i < NumberOfLanguagesInDatabase; i++)
			{
				writer.Write(OffsetInfo[i].LanguageId);
				writer.Write(OffsetInfo[i].Offset);
			}

			// Start writing
			for (int i = 0; i < NumberOfLanguagesInDatabase; i++)
			{
				// Get Language Data
				LanguageData languageData = Data[(SupportedLanguages) OffsetInfo[i].LanguageId];
				// Save new offset
				OffsetInfo[i].Offset = (int) writer.BaseStream.Position - (int) StartOffset;

				// Process data
				MemoryStream memoryStream = new MemoryStream();
				BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.Unicode);

				languageData.Cities.Write(binaryWriter);
				languageData.Clubs.Write(binaryWriter);
				languageData.Continents.Write(binaryWriter);
				languageData.Injuries.Write(binaryWriter);
				languageData.Nations.Write(binaryWriter);
				languageData.Awards.Write(binaryWriter);
				languageData.Stadiums.Write(binaryWriter);
				languageData.Stages.Write(binaryWriter);
				languageData.Competitions.Write(binaryWriter);
				languageData.Regions.Write(binaryWriter);
				languageData.PlayerTypes.Write(binaryWriter);
				languageData.MediaSources.Write(binaryWriter);
				languageData.LocalRegions.Write(binaryWriter);

				byte[] compressedData = ZLibCompressedStream.Compress(memoryStream.ToArray(), CompressionMethod.Deflated, CompressionLevel.Normal);
				writer.Write(compressedData);
			}

			// save offsets again
			writer.BaseStream.Position = offsetPosition;
			for (int i = 0; i < NumberOfLanguagesInDatabase; i++)
			{
				writer.Write(OffsetInfo[i].LanguageId);
				writer.Write(OffsetInfo[i].Offset);
			}

			writer.Close();
		}

		#endregion

		#region Private Methods

		private static void ReadLanguage(string fileName, int i, LanguageData languageData)
		{
			BinaryReader reader = new BinaryReader(File.OpenRead(fileName), Encoding.Unicode);

			// Calculate offsets
			long langOffset = StartOffset + OffsetInfo[i].Offset;
			long size;
			if (i + 1 < NumberOfLanguagesInDatabase - 1)
				size = OffsetInfo[i + 1].Offset - OffsetInfo[i].Offset;
			else
				size = reader.BaseStream.Length - -OffsetInfo[i].Offset;

			// Position at correct offset
			reader.BaseStream.Position = langOffset;

			// Read compressed data and convert to decompressed data
			byte[] compressedData = new byte[size];
			reader.Read(compressedData, 0, (int) size);
			byte[] decompressedData = ZLibCompressedStream.Decompress(compressedData);
			reader.Close();

			// Process data
			MemoryStream memoryStream = new MemoryStream(decompressedData);
			BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.Unicode);

			languageData.Cities.Read(binaryReader);
			languageData.Clubs.Read(binaryReader);
			languageData.Continents.Read(binaryReader);
			languageData.Injuries.Read(binaryReader);
			languageData.Nations.Read(binaryReader);
			languageData.Awards.Read(binaryReader);
			languageData.Stadiums.Read(binaryReader);
			languageData.Stages.Read(binaryReader);
			languageData.Competitions.Read(binaryReader);
			languageData.Regions.Read(binaryReader);
			languageData.PlayerTypes.Read(binaryReader);
			languageData.MediaSources.Read(binaryReader);
			languageData.LocalRegions.Read(binaryReader);

			// Save binDumpStream if necessary
			if (!string.IsNullOrEmpty(DebugBinaryDumpPath))
			{
				FileStream binDumpStream = new FileStream(DebugBinaryDumpPath + FILE_NAME + ".bindump." + OffsetInfo[i].LanguageId + ".dat", FileMode.Create);
				binDumpStream.Write(decompressedData, 0, decompressedData.Length);
				binDumpStream.Close();
			}

			// Save dataDumpStream if necessary
			if (!string.IsNullOrEmpty(DebugDataDumpPath))
			{
				string outputFileName = DebugDataDumpPath + FILE_NAME + ".{0}.datadump." + OffsetInfo[i].LanguageId + ".txt";
				languageData.Cities.Export(outputFileName);
				languageData.Clubs.Export(outputFileName);
				languageData.Continents.Export(outputFileName);
				languageData.Injuries.Export(outputFileName);
				languageData.Nations.Export(outputFileName);
				languageData.Awards.Export(outputFileName);
				languageData.Stadiums.Export(outputFileName);
				languageData.Stages.Export(outputFileName);
				languageData.Competitions.Export(outputFileName);
				languageData.Regions.Export(outputFileName);
				languageData.PlayerTypes.Export(outputFileName);
				languageData.MediaSources.Export(outputFileName);
				languageData.LocalRegions.Export(outputFileName);
			}

			memoryStream.Close();
			binaryReader.Close();
		}

		#endregion

		#region Nested type: OffsetInfoData

		private class OffsetInfoData
		{
			#region Public Properties

			public int LanguageId { get; set; }
			public int Offset { get; set; }

			#endregion
		}

		#endregion

		#region Nested type: ReaderBackgroundWorker

		internal class ReaderBackgroundWorker : BackgroundWorker
		{
			#region Public Properties

			public LanguageData Data { get; set; }
			public string FileName { get; set; }
			public int LanguageIndex { get; set; }
			public List<SupportedLanguages> Languages { get; set; }

			#endregion
		}

		#endregion
	}
}